﻿#region Copyright ©2008-2010, Technische Universitaet Muenchen
// ====================================================================================================
//
// Last Changed by       $Author: kalus $
// Last Changed Date     $LastChangedDate: 2009-07-30 15:53:01 +0200 (Thu, 30 Jul 2009) $
// Last Changed Revision $Rev: 259 $
//
// ====================================================================================================
#endregion

using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Linq;
using System.Xml;

namespace Tum.CollabXT.Simulator
{
   class SimulatorToolProvider : IToolProvider
   {
      #region Private Attributes
      private IProcessProvider _processProvider;
      string _outputFile;
      string _architectureFile;
      string _specificationFile;
      #endregion

      #region Public Properties
      public string OutputFile
      {
         get { return _outputFile; }
         set { _outputFile = value; }
      }

      public string ArchitectureFile
      {
         get { return _architectureFile; }
         set { _architectureFile = value; }
      }

      public string SpecificationFile
      {
         get { return _specificationFile; }
         set { _specificationFile = value; }
      }
      #endregion

      #region IToolProvider Members

      public static string GetProviderName()
      {
         return Resources.Language.ProviderInfo_Name;
      }

      public static string GetProviderDescription()
      {
         return Resources.Language.ProviderInfo_Description;
      }

      public static string GetProviderAuthor()
      {
         return "© 2010 TU München";
      }

      public void Initialize(IProcessProvider processProvider, Log log)
      {
         _processProvider = processProvider;
      }

      class RoleToProductDependencyCount
      {
         public int Responsible { get; set; }

         public int Participating { get; set; }

         public RoleToProductDependencyCount(int responsible, int participating)
         {
            Responsible = responsible;
            Participating = participating;
         }
      }

      public void Process()
      {
         Architecture arch = null;
         if (!string.IsNullOrEmpty(_architectureFile))
            arch = ReadArchitectureFile();

         var productSpecs = new Dictionary<string, ProductSpecification>();
         if (!string.IsNullOrEmpty(_specificationFile))
            productSpecs = ReadSpecificationFile();

         // create a writer and open the file
         using (TextWriter tw = new StreamWriter(_outputFile, false, System.Text.Encoding.Unicode))
         {
            tw.WriteLine("#####################################################");
            tw.WriteLine("            ERZEUGENDE ABHÄNGIGKEITEN                ");
            tw.WriteLine("#####################################################");
            foreach (IArtifact product in _processProvider.Artifacts)
            {
               if (product.IsInitial)
               {
                  tw.WriteLine(product.Name);
                  WriteGeneratedProductsRecursive(tw, product, 1);
               }
            }

            tw.WriteLine("#####################################################");
            tw.WriteLine("               PROJEKTABLAUF                         ");
            tw.WriteLine("#####################################################");

            var productInstances = new Dictionary<string, List<ProductInstance>>();

            var productInstanceCount = new Dictionary<string, int>();
            var roleToProductCount = new Dictionary<string, RoleToProductDependencyCount>();

            tw.WriteLine("Erstelle Initiale Produkte");
            foreach (IArtifact product in _processProvider.Artifacts)
            {
               if (product.IsInitial)
               {
                  string productInstanceName = product.Name;
                  var inst = new ProductInstance(productInstanceName, 1);
                  if (!productInstances.ContainsKey(product.Id))
                  {
                     productInstances.Add(product.Id, new List<ProductInstance>());
                     productInstances[product.Id].Add(inst);
                  }

                  tw.WriteLine((product.IsExternal ? "\tErhalte Produkt: " : "\tErstelle Produkt: ") + productInstanceName);
                  UpdateStatisticsCounters(product,
                     productInstanceCount,
                     roleToProductCount);
               }
            }

            tw.WriteLine("Beginne Projektablauf");
            foreach (var milestone in _processProvider.Milestones)
            {
               // Erstelle die Produkte für diesen Meilenstein
               foreach (IArtifact product in _processProvider.GetArtifactsByMilestoneID(milestone.Id))
               {
                  if (!product.IsInitial)
                  {
                     ProductSpecification.CreationType creationType = ProductSpecification.CreationType.ForMilestone;
                     if (productSpecs.ContainsKey(product.Id))
                     {
                        creationType = productSpecs[product.Id].Creation;
                     }

                     if (SystemElementTypes.ProductIds.ContainsKey(product.Name))
                     {
                        CreateSystemElements(tw, arch,
                           productInstances, productInstanceCount, roleToProductCount);
                     }
                     else
                     {
                        CreateOrUpdateProduct(tw, product, arch, milestone,
                           creationType, productInstances, productInstanceCount, roleToProductCount);
                     }
                  }
               }

               tw.WriteLine("Meilenstein erreicht: " + milestone.Name + " " + milestone.ScheduledDate.ToShortDateString());
            }

            tw.WriteLine("#####################################################");
            tw.WriteLine("               ERSTELLTE PRODUKTE                    ");
            tw.WriteLine("#####################################################");
            int sum = 0;
            foreach (KeyValuePair<string, int> createdProductInstance in productInstanceCount)
            {
               tw.WriteLine(createdProductInstance.Key + ": " + createdProductInstance.Value);
               sum += createdProductInstance.Value;
            }
            tw.WriteLine("-----------------------------------------------------");
            tw.WriteLine("Summe: " + sum);

            tw.WriteLine("#####################################################");
            tw.WriteLine("          NICHT ERSTELLTE PRODUKTE                   ");
            tw.WriteLine("#####################################################");
            foreach (IArtifact product in _processProvider.Artifacts)
            {
               if (!productInstanceCount.ContainsKey(product.Name))
               {
                  tw.WriteLine(product.Name);
               }
            }

            tw.WriteLine("#####################################################");
            tw.WriteLine("                    ROLLEN                           ");
            tw.WriteLine("#####################################################");
            foreach (KeyValuePair<string, RoleToProductDependencyCount> roleToProduct in roleToProductCount)
            {
               tw.WriteLine(roleToProduct.Key + " - Verantwortlich: " + roleToProduct.Value.Responsible +
                  ", Mitwirkend: " + roleToProduct.Value.Participating);
            }

            tw.WriteLine("#####################################################");
            tw.WriteLine("  ROLLEN OHNE VERANTWORTUNG UND MITWIRKUNG           ");
            tw.WriteLine("#####################################################");
            foreach (IRole role in _processProvider.Roles)
            {
               if (!roleToProductCount.ContainsKey(role.Name))
               {
                  tw.WriteLine(role.Name);
               }
            }

            // close the stream
            tw.Close();
         }
      }

      private void CreateSystemElements(TextWriter tw, Architecture arch, Dictionary<string, List<ProductInstance>> productInstances, Dictionary<string, int> productInstanceCount, Dictionary<string, RoleToProductDependencyCount> roleToProductCount)
      {
         foreach (SystemElement sysElem in arch.Nodes)
         {
            if (sysElem.Type != SystemElementTypes.System)
            {
               IArtifact sysElemProduct = _processProvider.GetArtifactByID(SystemElementTypes.ProductIds[sysElem.Type]);

               if (!productInstances.ContainsKey(sysElemProduct.Id))
               {
                  productInstances.Add(sysElemProduct.Id, new List<ProductInstance>());

                  string localInstanceName = sysElemProduct.Name + " [" + sysElem.Type + ": " + sysElem.Name + "]";
                  var inst = new ProductInstance(localInstanceName, 1) {SystemElement = sysElem};
                  productInstances[sysElemProduct.Id].Add(inst);

                  tw.WriteLine((sysElemProduct.IsExternal ? "\tErhalte Produkt: " : "\tErstelle Produkt: ") + localInstanceName);
                  UpdateStatisticsCounters(sysElemProduct,
                     productInstanceCount,
                     roleToProductCount);
               }
               else
               {
                  bool found = false;
                  foreach (var instance in productInstances[sysElemProduct.Id])
                  {
                     if (instance.SystemElement != null && instance.SystemElement == sysElem)
                     {
                        instance.Version++;
                        tw.WriteLine("\tErstelle neue Version von: " + instance.Name);
                        found = true;
                        break;
                     }
                  }

                  if (!found)
                  {
                     string localInstanceName = sysElemProduct.Name + " [" + sysElem.Type + ": " + sysElem.Name + "]";
                     var inst = new ProductInstance(localInstanceName, 1) {SystemElement = sysElem};
                     productInstances[sysElemProduct.Id].Add(inst);

                     tw.WriteLine("\tErstelle Produkt: " + localInstanceName);
                     UpdateStatisticsCounters(sysElemProduct,
                        productInstanceCount,
                        roleToProductCount);
                  }
               }
            }
         }
      }

      private void CreateOrUpdateProduct(TextWriter tw, IArtifact product, Architecture arch,
         IMilestone milestone, ProductSpecification.CreationType creationType, 
         IDictionary<string, List<ProductInstance>> productInstances, Dictionary<string, int> productInstanceCount, 
         Dictionary<string, RoleToProductDependencyCount> roleToProductCount)
      {
         switch (creationType)
         {
            case ProductSpecification.CreationType.Once:
               {
                  if (!productInstances.ContainsKey(product.Id))
                  {
                     productInstances.Add(product.Id, new List<ProductInstance>());

                     string localInstanceName = product.Name;
                     var inst = new ProductInstance(localInstanceName, 1);
                     productInstances[product.Id].Add(inst);

                     tw.WriteLine((product.IsExternal ? "\tErhalte Produkt: " : "\tErstelle Produkt: ") + localInstanceName);
                     UpdateStatisticsCounters(product,
                        productInstanceCount,
                        roleToProductCount);
                  }
                  else
                  {
                     productInstances[product.Id][0].Version++;
                     tw.WriteLine("\tErstelle neue Version von: " + productInstances[product.Id][0].Name);
                  }
               }
               break;
            case ProductSpecification.CreationType.ForEachSystemElement:
               {
                  foreach (SystemElement sysElement in arch.Nodes)
                  {
                     if (sysElement.Type != SystemElementTypes.System)
                     {
                        if (productInstances.ContainsKey(product.Id))
                        {
                           bool found = false;
                           foreach (ProductInstance inst in productInstances[product.Id])
                           {
                              // da ist die instanz
                              if (inst.SystemElement != null && inst.SystemElement == sysElement)
                              {
                                 inst.Version++;
                                 tw.WriteLine("\tErstelle neue Version von: " + inst.Name);
                                 found = true;
                                 break;
                              }
                           }

                           if (!found)
                           {
                              string localInstanceName = product.Name + " [" + sysElement.Type + ": " + sysElement.Name + "]";
                              var inst = new ProductInstance(localInstanceName, 1) {SystemElement = sysElement};
                              productInstances[product.Id].Add(inst);

                              tw.WriteLine("\tErstelle Produkt: " + localInstanceName);
                              UpdateStatisticsCounters(product,
                                 productInstanceCount,
                                 roleToProductCount);
                           }
                        }
                        else
                        {
                           productInstances.Add(product.Id, new List<ProductInstance>());
                           string localInstanceName = product.Name + " [" + sysElement.Type + ": " + sysElement.Name + "]";
                           var inst = new ProductInstance(localInstanceName, 1) {SystemElement = sysElement};
                           productInstances[product.Id].Add(inst);

                           tw.WriteLine("\tErstelle Produkt: " + localInstanceName);
                           UpdateStatisticsCounters(product,
                              productInstanceCount,
                              roleToProductCount);
                        }
                     }
                  }
               }
               break;
            default:
               {
                  string localInstanceName = product.Name + " (" + milestone.Name + " " + milestone.ScheduledDate.ToShortDateString() + ")";
                  var inst = new ProductInstance(localInstanceName, 1);
                  if (!productInstances.ContainsKey(product.Id))
                     productInstances.Add(product.Id, new List<ProductInstance>());
                  productInstances[product.Id].Add(inst);

                  tw.WriteLine((product.IsExternal ? "\tErhalte Produkt: " : "\tErstelle Produkt: ") + localInstanceName);
                  UpdateStatisticsCounters(product,
                     productInstanceCount,
                     roleToProductCount);
               }
               break;
         }
      }

      private Dictionary<string, ProductSpecification> ReadSpecificationFile()
      {
         XDocument xmlDoc = XDocument.Load(_specificationFile);

         var specList = new Dictionary<string, ProductSpecification>();

         if (xmlDoc.Root != null)
         {
            var productsNode = xmlDoc.Root.Element("products");
            foreach (var productNode in productsNode.Elements())
            {
               string id = productNode.Attribute("id").Value;
               IArtifact productType = _processProvider.GetArtifactByID(id);

               string creationString = productNode.Element("creation").Value;
               ProductSpecification.CreationType creationType;
               switch (creationString)
               {
                  case "once":
                     creationType = ProductSpecification.CreationType.Once;
                     break;
                  case "forEachSystemElement":
                     creationType = ProductSpecification.CreationType.ForEachSystemElement;
                     break;
                  default:
                     creationType = ProductSpecification.CreationType.ForMilestone;
                     break;
               }
               var spec = new ProductSpecification(productType, creationType);
               specList.Add(id, spec);
            }
         }

         return specList;
      }

      private static void ReadArchitectureRecursive(Architecture arch, SystemElement parentNode, XElement xElem)
      {
         foreach (var xmlNode in xElem.Elements())
         {
            // TODO: consistency checks. A HW-Node can't be under a SW node and so on
            //switch (xmlNode.Name.ToString())
            //{
            //   case ArchNodeTypes.SWUnit:
            //      break;

            //   case ArchNodeTypes.SWComponent:
            //      break;

            //   case ArchNodeTypes.SWModule:
            //      break;

            //   default:
            //      break;
            //}

            string type = xmlNode.Name.ToString();
            string name = xmlNode.Attribute("name").Value;
            var node = new SystemElement(name, type, parentNode);
            arch.Nodes.Add(node);

            ReadArchitectureRecursive(arch, node, xmlNode);
         }
      }

      private Architecture ReadArchitectureFile()
      {
         XDocument xmlDoc = XDocument.Load(_architectureFile);

         var arch = new Architecture();

         if (xmlDoc.Root != null)
         {
            var systemNode = xmlDoc.Root.Element(SystemElementTypes.System);
            if (systemNode != null)
            {
               string systemName = systemNode.Attribute("name").Value;
               var archSystemNode = new SystemElement(systemName, SystemElementTypes.System, null);
               arch.Nodes.Add(archSystemNode);

               ReadArchitectureRecursive(arch, archSystemNode, systemNode);
            }
         }

         return arch;
      }

      private void UpdateStatisticsCounters(IArtifact product,
         IDictionary<string, int> numberOfProductInstances,
         IDictionary<string, RoleToProductDependencyCount> roleToProductCount)
      {
         if (!numberOfProductInstances.ContainsKey(product.Name))
            numberOfProductInstances.Add(product.Name, 1);
         else
            numberOfProductInstances[product.Name]++;

         foreach (IRole responsibleRole in _processProvider.GetRolesResponsibleByArtifactID(product.Id))
         {
            string roleName = responsibleRole.Name;
            if (!roleToProductCount.ContainsKey(roleName))
               roleToProductCount.Add(roleName, new RoleToProductDependencyCount(1, 0));
            else
               roleToProductCount[roleName].Responsible++;
         }

         foreach (IRole participatingRole in _processProvider.GetRolesParticipatingByArtifactID(product.Id))
         {
            string roleName = participatingRole.Name;
            if (!roleToProductCount.ContainsKey(roleName))
               roleToProductCount.Add(roleName, new RoleToProductDependencyCount(0, 1));
            else
               roleToProductCount[roleName].Participating++;
         }
      }

      private void WriteGeneratedProductsRecursive(TextWriter tw, IArtifact product, int depth)
      {
         string indent = string.Empty;
         for (int i = 0; i < depth; i++)
         {
            indent += "\t";
         }

         foreach (IArtifact generatedProduct in _processProvider.GetGeneratedArtifactsByArtifactID(product.Id))
         {
            tw.WriteLine(indent + generatedProduct.Name);
            WriteGeneratedProductsRecursive(tw, generatedProduct, depth + 1);
         }
      }

      public string GetConfigPageName()
      {
         return "SimulatorConfigPage.xaml";
      }

      #endregion

      #region IProvider Members

      public Dictionary<string, object> ExtendedData
      {
         get { throw new NotImplementedException(); }
      }

      public void Serialize(XmlNode outputParentNode)
      {
         XmlDocument xmlDoc = outputParentNode.OwnerDocument;

         XmlNode settingsNode = xmlDoc.CreateElement("settings");
         outputParentNode.AppendChild(settingsNode);

         XmlAttribute outputFileAttribute = xmlDoc.CreateAttribute("outputFile");
         outputFileAttribute.Value = OutputFile;
         settingsNode.Attributes.Append(outputFileAttribute);

         XmlAttribute architectureFileAttribute = xmlDoc.CreateAttribute("architectureFile");
         architectureFileAttribute.Value = ArchitectureFile;
         settingsNode.Attributes.Append(architectureFileAttribute);

         XmlAttribute specificationFileAttribute = xmlDoc.CreateAttribute("specificationFile");
         specificationFileAttribute.Value = SpecificationFile;
         settingsNode.Attributes.Append(specificationFileAttribute);
      }

      public void Deserialize(XmlNode inputParentNode)
      {
         XmlNode settingsNode = inputParentNode.SelectSingleNode("settings");

         try
         {
            OutputFile = null;
            OutputFile = settingsNode.Attributes["outputFile"].Value;
         }
         catch (Exception)
         { }

         try
         {
            ArchitectureFile = null;
            ArchitectureFile = settingsNode.Attributes["architectureFile"].Value;
            if (!File.Exists(ArchitectureFile))
               ArchitectureFile = null;
         }
         catch (Exception)
         { }

         try
         {
            SpecificationFile = null;
            SpecificationFile = settingsNode.Attributes["specificationFile"].Value;
            if (!File.Exists(SpecificationFile))
               SpecificationFile = null;
         }
         catch (Exception)
         { }
      }

      #endregion
   }
}
